Compare commits

...

7 Commits

Author SHA1 Message Date
士子☀️ 73ae0ad083 Update README.md 2022-01-27 11:38:36 +08:00
caorushizi 5ed6e84a14 Merge branch 'master' of https://github.com/caorushizi/m3u8-downloader 2022-01-27 00:22:22 +08:00
caorushizi ed45cd8d73 README.md 2022-01-27 00:21:36 +08:00
士子☀️ f393b99cb5 Update README.md 2022-01-27 00:07:56 +08:00
caorushizi 6638600ffa 修复开发环境中没有注入环境变量导致无法运行 2022-01-26 23:53:54 +08:00
士子☀️ 4d5cae3b0c Update build.js 2022-01-26 13:38:31 +08:00
caorushizi e846390d04 添加数据统计许可 2022-01-25 23:52:07 +08:00
14 changed files with 144 additions and 94 deletions
+3
View File
@@ -1 +1,4 @@
VITE_APP_TDID=
VITE_APP_SENTRY_DSN=
NODE_ENV=development
+3
View File
@@ -1 +1,4 @@
VITE_APP_TDID=
VITE_APP_SENTRY_DSN=
NODE_ENV=production
+23 -16
View File
@@ -1,32 +1,39 @@
# media-downloader
m3u8 视频在线提取工具 流媒体下载 m3u8下载 桌面客户端 windows mac
# 功能简介
m3u8 视频在线提取工具 流媒体下载 m3u8下载 桌面客户端 windows mac
可以直接在线获取 m3u8 链接地址,无需使用使用网络抓包,无需安装浏览器插件,可以直接带出请求标头……
# 下载链接
# 上手指南
以下指南将帮助你在本地机器上安装和运行该项目,进行开发和测试。关于如何将该项目部署到在线环境,请参考部署小节。
# 安装要求
运行代码需要 node 和 yarn,node需要在官网下载安装,yarn可以通过`npm i -g yarn`安装。
# 运行代码
开发环境 `yarn run watch`
打包运行 `yarn run publish`
# 下载链接
- [v1.1.3 windows](https://github.com/caorushizi/m3u8-downloader/releases/download/1.1.3/media-downloader-setup-1.1.3.exe)
- [v1.0.3 windows](http://static.ziying.site/media-downloader-1.0.3%20Setup.exe)
- [v1.0.2 windows](http://static.ziying.site/media-downloader-1.0.2%20Setup.exe)
- [v1.0.1 windows](http://static.ziying.site/media-downloader-1.0.1%20Setup.exe)
- [v1.0.0 windows](http://static.ziying.site/media-downloader-1.0.0%20Setup.exe)
# 更新日志 2021.04.18
1. 新增视频批量下载功能;
2. 添加收藏功能;
3. 更新了界面样式;
# 软件截图
![首页](http://static.ziying.site/2021-04-29-home.png)
![首页](http://static.ziying.site/Snipaste_2022-01-08_18-43-25.png)
![浏览器页面](http://static.ziying.site/downloader-browser.png)
![设置页面](http://static.ziying.site/Snipaste_2022-01-08_18-43-39.png)
![收藏页面](http://static.ziying.site/2021-04-29-fav.png)
# 技术栈
- vite https://cn.vitejs.dev
- antd https://ant.design
- electron https://www.electronjs.org
![设置页面](http://static.ziying.site/2021-04-29-setting.png)
# 鸣谢
N_m3u8DL-CLI 来自于 https://github.com/nilaoda/N_m3u8DL-CLI
# 使用 Vite & esBuild 构建
mediago 来自于 https://github.com/caorushizi/mediago
![vite esbuild](http://static.ziying.site/vite+esbuild.png)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "media-downloader",
"version": "1.1.2",
"version": "1.1.4",
"description": "在线视频下载",
"author": "caorushizi",
"main": "dist/main/index.js",
+10 -5
View File
@@ -1,14 +1,19 @@
const { build } = require("vite");
const fs = require("fs");
const { resolve } = require("path");
const { build } = require("vite");
const rimraf = require("rimraf");
rimraf.sync(resolve(__dirname, "../dist"));
const { parsed } = require("dotenv").config({
path: resolve(__dirname, `../.env.${process.env.NODE_ENV}.local`),
});
let envPath = resolve(__dirname, `../.env.${process.env.NODE_ENV}.local`);
if (!fs.existsSync(envPath)) {
envPath = resolve(__dirname, `../.env.${process.env.NODE_ENV}`);
}
const mainDefined = Object.keys(parsed).reduce((prev, cur) => {
const { parsed } = require("dotenv").config({ path: envPath });
const mainDefined = Object.keys(parsed || {}).reduce((prev, cur) => {
prev[`process.env.${[cur]}`] = JSON.stringify(parsed[cur]);
return prev;
}, {});
+11 -7
View File
@@ -1,17 +1,21 @@
const fs = require("fs");
const { resolve, join } = require("path");
const { spawn } = require("child_process");
const { createServer } = require("vite");
const chalk = require("chalk");
const { resolve } = require("path");
const electron = require("electron");
const reactRefresh = require("@vitejs/plugin-react-refresh");
const path = require("path");
const { spawn } = require("child_process");
let electronProcess = null;
let manualRestart = false;
require("dotenv").config({
path: resolve(__dirname, `../.env.${process.env.NODE_ENV}.local`),
});
let envPath = resolve(__dirname, `../.env.${process.env.NODE_ENV}.local`);
if (!fs.existsSync(envPath)) {
envPath = resolve(__dirname, `../.env.${process.env.NODE_ENV}`);
}
require("dotenv").config({ path: envPath });
function startMain() {
return require("esbuild").build({
@@ -82,7 +86,7 @@ function startRenderer() {
}
function startElectron() {
let args = ["--inspect=5858", path.join(__dirname, "../dist/main/index.js")];
let args = ["--inspect=5858", join(__dirname, "../dist/main/index.js")];
electronProcess = spawn(String(electron), args);
+1
View File
@@ -22,6 +22,7 @@ export default function handleStore(): void {
tip: true,
proxy: "",
useProxy: false,
statistics: true,
},
});
+8 -6
View File
@@ -12,12 +12,14 @@ import handleUpdater from "main/helper/handleUpdater";
Sentry.init({ dsn: process.env.VITE_APP_SENTRY_DSN });
crashReporter.start({
companyName: author,
productName: name,
ignoreSystemCrashHandler: true,
submitURL: process.env.VITE_APP_SENTRY_DSN,
});
if (process.env.VITE_APP_SENTRY_DSN) {
crashReporter.start({
companyName: author,
productName: name,
ignoreSystemCrashHandler: true,
submitURL: process.env.VITE_APP_SENTRY_DSN,
});
}
if (require("electron-squirrel-startup")) {
app.quit();
@@ -1,5 +1,5 @@
import React, { FC, useEffect, useRef, useState } from "react";
import { Button, FormInstance, Space, Switch, Tooltip } from "antd";
import React, { FC, useRef } from "react";
import { Button, FormInstance, Space, Switch, Tooltip, Form } from "antd";
import "./index.scss";
import ProForm, {
ProFormGroup,
@@ -7,7 +7,7 @@ import ProForm, {
ProFormSwitch,
ProFormText,
} from "@ant-design/pro-form";
import { FolderOpenOutlined } from "@ant-design/icons";
import { FolderOpenOutlined, QuestionCircleOutlined } from "@ant-design/icons";
import { AppState } from "renderer/store/reducers";
import {
Settings,
@@ -17,30 +17,33 @@ import { useDispatch, useSelector } from "react-redux";
import { Box } from "@chakra-ui/react";
import { version } from "../../../../../../package.json";
import { downloaderOptions } from "renderer/utils/variables";
import useElectron from "renderer/hooks/electron";
interface FormData {
exeFile: string;
workspace: string;
tip: boolean;
proxy: string;
}
const statisticsTooltip = `
是否允许统计用户数据
1. 统计数据不会用于商业用途,仅仅用于优化用户体验
2. 关闭用户统计依然会收集打开页面的次数,但不会收集任何自定义数据
3. 软件会统计页面报错,以便排查错误,请谅解~
`;
// 设置页面
const Setting: FC = () => {
const settings = useSelector<AppState, Settings>((state) => state.settings);
const dispatch = useDispatch();
const formRef = useRef<FormInstance<FormData>>();
const [proxyChecked, setProxyChecked] = useState<boolean>(false);
useEffect(() => {
const { useProxy } = settings;
setProxyChecked(useProxy);
}, []);
const formRef = useRef<FormInstance<Settings>>();
const {
store,
getPath,
showOpenDialog,
openConfigDir: openConfigDirElectron,
openBinDir: openBinDirElectron,
openPath,
} = useElectron();
// 选择下载地址
const handleSelectDir = async (): Promise<void> => {
const defaultPath = await window.electron.getPath("documents");
const { filePaths } = await window.electron.showOpenDialog({
const defaultPath = await getPath("documents");
const { filePaths } = await showOpenDialog({
defaultPath,
properties: ["openDirectory"],
});
@@ -49,7 +52,7 @@ const Setting: FC = () => {
// 返回值为空
if (Array.isArray(filePaths) && filePaths.length <= 0) return;
const workspaceValue = filePaths[0];
await window.electron.store.set("workspace", workspaceValue);
await store.set("workspace", workspaceValue);
formRef.current?.setFieldsValue({
workspace: workspaceValue || "",
});
@@ -59,55 +62,54 @@ const Setting: FC = () => {
// 打开配置文件文件夹
const openConfigDir = async (): Promise<void> => {
window.electron.openConfigDir();
openConfigDirElectron();
};
// 打开可执行程序文件夹
const openBinDir = () => {
window.electron.openBinDir();
openBinDirElectron();
};
// 本地存储文件夹
const localDir = async (): Promise<void> => {
const { workspace } = settings;
window.electron.openPath(workspace);
await openPath(workspace);
};
// 更改代理设置
const toggleProxySetting = async (enableProxy: boolean): Promise<void> => {
setProxyChecked(enableProxy);
await window.electron.store.set("useProxy", enableProxy);
};
const { workspace, exeFile, tip, proxy } = settings;
const { useProxy } = settings;
return (
<Box className="setting-form">
<ProForm<FormData>
<ProForm<Settings>
formRef={formRef}
layout="horizontal"
submitter={false}
labelCol={{ style: { width: "130px" } }}
labelAlign={"left"}
size={"small"}
colon={false}
initialValues={{ workspace, exeFile, tip, proxy }}
initialValues={settings}
onValuesChange={async (changedValue) => {
if (Object.keys(changedValue).includes("tip")) {
await window.electron.store.set("tip", changedValue["tip"]);
}
if (Object.keys(changedValue).includes("exeFile")) {
const value = changedValue["exeFile"];
await window.electron.store.set("exeFile", value);
dispatch(updateSettings({ exeFile: value }));
}
// 代理 onchange 事件
if (Object.keys(changedValue).includes("proxy")) {
const value = changedValue["proxy"];
await window.electron.store.set("proxy", value);
if (proxyChecked) {
await toggleProxySetting(false);
for (const key in changedValue) {
if (changedValue.hasOwnProperty(key)) {
const value = changedValue[key];
await store.set(key, value);
// 如果修改代理地址,关闭代理,可以手动打开
if (key === "proxy" && useProxy) {
await store.set("useProxy", false);
const form = formRef.current;
if (form) {
form.setFieldsValue({
...settings,
useProxy: false,
proxy: value,
});
}
}
}
}
dispatch(updateSettings({ ...settings, ...changedValue }));
}}
>
<ProFormGroup label="基础设置" direction={"vertical"}>
@@ -128,18 +130,33 @@ const Setting: FC = () => {
name="proxy"
placeholder="请填写代理地址"
label="代理设置"
fieldProps={{
addonAfter: (
<Tooltip title="该代理会对软件自带浏览器以及下载时生效">
<Switch
checked={proxyChecked}
checkedChildren="代理生效"
unCheckedChildren="代理关闭"
onChange={toggleProxySetting}
/>
/>
<ProFormSwitch
name={"useProxy"}
label={
<Box d={"flex"} flexDirection={"row"} alignItems={"center"}>
<Box mr={5}></Box>
<Tooltip
title={"该代理会对软件自带浏览器以及下载时生效"}
placement={"right"}
>
<QuestionCircleOutlined />
</Tooltip>
),
}}
</Box>
}
>
<Switch />
</ProFormSwitch>
<ProFormSwitch
label={
<Box d={"flex"} flexDirection={"row"} alignItems={"center"}>
<Box mr={5}></Box>
<Tooltip title={statisticsTooltip} placement={"right"}>
<QuestionCircleOutlined />
</Tooltip>
</Box>
}
name="statistics"
/>
</ProFormGroup>
<ProFormGroup label="下载设置" direction={"vertical"}>
+3 -2
View File
@@ -1,6 +1,6 @@
import React, { FC, useEffect, useRef, useState } from "react";
import "./index.scss";
import { Badge, Button, Drawer, message, Tabs } from "antd";
import { Badge, Button, message, Tabs } from "antd";
import WindowToolBar from "renderer/components/WindowToolBar";
import Setting from "renderer/nodes/main/elements/Setting";
import { SourceStatus, SourceType } from "renderer/types";
@@ -45,7 +45,7 @@ const MainPage: FC = () => {
(state) => state.main
);
countRef.current = notifyCount;
const { workspace } = settings;
const { workspace, exeFile } = settings;
const {
addEventListener,
removeEventListener,
@@ -80,6 +80,7 @@ const MainPage: FC = () => {
): Promise<void> => {
const item: SourceItem = {
...source,
exeFile,
status: SourceStatus.Ready,
type: SourceType.M3u8,
directory: settings.workspace,
@@ -4,6 +4,7 @@ export interface Settings {
proxy: string;
useProxy: boolean;
exeFile: string;
statistics: boolean; // 是否允许打点统计
}
export const UPDATE_SETTINGS = "UPDATE_SETTINGS";
+1
View File
@@ -10,6 +10,7 @@ const initialState: Settings = {
tip: true,
proxy: "",
useProxy: false,
statistics: true,
};
export default function settings(
+5 -1
View File
@@ -1,4 +1,5 @@
import { version } from "../../../package.json";
import store from "renderer/store";
const isProd = process.env.NODE_ENV === "production";
@@ -21,7 +22,10 @@ class TDEvent {
}
onEvent(eventId: string, mapKv: any = {}) {
window.TDAPP.onEvent(eventId, "", mapKv);
const { settings } = store.getState();
if (settings.statistics) {
window.TDAPP.onEvent(eventId, "", mapKv);
}
}
}
+1
View File
@@ -137,6 +137,7 @@ declare interface AppStore {
proxy: string;
useProxy: boolean;
exeFile: string;
statistics: boolean; // 是否允许打点统计
}
declare interface Manifest {