mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-04-21 00:12:05 +00:00
Downloading without visual feedback
This commit is contained in:
parent
66c56f3522
commit
7dc72a1507
20 changed files with 238 additions and 83 deletions
3
@types/crunchyTypes.d.ts
vendored
3
@types/crunchyTypes.d.ts
vendored
|
|
@ -46,7 +46,8 @@ export type CrunchyEpMeta = {
|
|||
episodeTitle: string,
|
||||
seasonID: string,
|
||||
season: number,
|
||||
showID: string
|
||||
showID: string,
|
||||
e: string
|
||||
}
|
||||
|
||||
export type DownloadedMedia = {
|
||||
|
|
|
|||
8
@types/hls-download.d.ts
vendored
8
@types/hls-download.d.ts
vendored
|
|
@ -1,11 +1,5 @@
|
|||
export type ProgressData = {
|
||||
total: number,
|
||||
cur: number,
|
||||
percent: number|string,
|
||||
time: number,
|
||||
downloadSpeed: number
|
||||
};
|
||||
declare module 'hls-download' {
|
||||
import type { ProgressData } from './messageHandler';
|
||||
export type HLSCallback = (data: ProgressData) => unknown;
|
||||
export default class hlsDownload {
|
||||
constructor(options: {
|
||||
|
|
|
|||
15
@types/messageHandler.d.ts
vendored
15
@types/messageHandler.d.ts
vendored
|
|
@ -9,7 +9,8 @@ export interface MessageHandler {
|
|||
availableDubCodes: () => Promise<string[]>,
|
||||
handleDefault: (name: string) => Promise<any>,
|
||||
resolveItems: (data: ResolveItemsData) => Promise<ResponseBase<QueueItem[]>>,
|
||||
listEpisodes: (id: string) => Promise<EpisodeListResponse>
|
||||
listEpisodes: (id: string) => Promise<EpisodeListResponse>,
|
||||
downloadItem: (data) => void
|
||||
}
|
||||
|
||||
export type QueueItem = {
|
||||
|
|
@ -61,7 +62,7 @@ export type FuniEpisodeData = {
|
|||
episode: string,
|
||||
episodeID: string,
|
||||
seasonTitle: string,
|
||||
seasonNumber: string
|
||||
seasonNumber: string,
|
||||
};
|
||||
|
||||
export type AuthData = { username: string, password: string };
|
||||
|
|
@ -72,6 +73,7 @@ export type FuniStreamData = { q: number, callback?: HLSCallback, x: number, fil
|
|||
timeout: number, partsize: number, fsRetryTime: number, noaudio?: boolean, mp4: boolean, ass: boolean, fontSize: number, fontName?: string, skipmux?: boolean,
|
||||
forceMuxer: AvailableMuxer | undefined, simul: boolean, skipSubMux: boolean, nocleanup: boolean }
|
||||
export type FuniSubsData = { nosubs?: boolean, sub: boolean, dlsubs: string[] }
|
||||
export type DownloadData = { id: string, e: string, dubLang: string[], fileName: string, q: number }
|
||||
|
||||
export type AuthResponse = ResponseBase<undefined>;
|
||||
export type FuniSearchReponse = ResponseBase<FunimationSearch>;
|
||||
|
|
@ -79,6 +81,7 @@ export type FuniShowResponse = ResponseBase<FuniEpisodeData[]>;
|
|||
export type FuniGetEpisodeResponse = ResponseBase<undefined>;
|
||||
export type CheckTokenResponse = ResponseBase<undefined>;
|
||||
|
||||
|
||||
export type ResponseBase<T> = ({
|
||||
isOk: true,
|
||||
value: T
|
||||
|
|
@ -87,4 +90,12 @@ export type ResponseBase<T> = ({
|
|||
reason: Error
|
||||
});
|
||||
|
||||
export type ProgressData = {
|
||||
total: number,
|
||||
cur: number,
|
||||
percent: number|string,
|
||||
time: number,
|
||||
downloadSpeed: number
|
||||
};
|
||||
|
||||
export type PossibleMessanges = keyof ServiceHandler;
|
||||
3
@types/randomEvents.d.ts
vendored
3
@types/randomEvents.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
|||
import { ProgressData } from "hls-download";
|
||||
|
||||
export type RandomEvents = {
|
||||
progress: ProgressData
|
||||
progress: ProgressData,
|
||||
finish: undefined
|
||||
}
|
||||
3
TODO.md
Normal file
3
TODO.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
- [ ] Hls-Download force yes or no on rewrite promt
|
||||
- [ ] Pick up if a download is currently in progress
|
||||
- [ ] Send more information with the progress event like the title and image to display more information
|
||||
47
crunchy.ts
47
crunchy.ts
|
|
@ -253,7 +253,7 @@ export default class Crunchy implements ServiceClass {
|
|||
}
|
||||
else{
|
||||
if(Date.now() > new Date(this.token.expires).getTime()){
|
||||
console.log('[WARN] The token has expired compleatly. I will try to refresh the token anyway, but you might have to reauth.');
|
||||
//console.log('[WARN] The token has expired compleatly. I will try to refresh the token anyway, but you might have to reauth.');
|
||||
}
|
||||
const authData = new URLSearchParams({
|
||||
'refresh_token': this.token.refresh_token,
|
||||
|
|
@ -387,7 +387,6 @@ export default class Crunchy implements ServiceClass {
|
|||
const toSend = searchResults.items.filter(a => a.type === 'series' || a.type === 'movie_listing');
|
||||
return { isOk: true, value: toSend.map(a => {
|
||||
return a.items.map((a): SearchResponseItem => {
|
||||
fs.writeFileSync('../test.json', JSON.stringify(a.images.poster_tall, null, 2));
|
||||
const images = (a.images.poster_tall ?? [[ { source: '/notFound.png' } ]])[0];
|
||||
return {
|
||||
id: a.id,
|
||||
|
|
@ -723,24 +722,6 @@ export default class Crunchy implements ServiceClass {
|
|||
if(item.season_title == '' && item.series_title == ''){
|
||||
item.season_title = 'NO_TITLE';
|
||||
}
|
||||
// set data
|
||||
const epMeta: CrunchyEpMeta = {
|
||||
data: [
|
||||
{
|
||||
mediaId: item.id
|
||||
}
|
||||
],
|
||||
seasonTitle: item.season_title,
|
||||
episodeNumber: item.episode,
|
||||
episodeTitle: item.title,
|
||||
seasonID: item.season_id,
|
||||
season: item.season_number,
|
||||
showID: id
|
||||
};
|
||||
if(item.playback){
|
||||
epMeta.data[0].playback = item.playback;
|
||||
}
|
||||
// find episode numbers
|
||||
const epNum = item.episode;
|
||||
let isSpecial = false;
|
||||
item.isSelected = false;
|
||||
|
|
@ -756,6 +737,25 @@ export default class Crunchy implements ServiceClass {
|
|||
? 'S' + epNumList.sp.toString().padStart(epNumLen, '0')
|
||||
: '' + parseInt(epNum, 10).toString().padStart(epNumLen, '0')
|
||||
);
|
||||
// set data
|
||||
const epMeta: CrunchyEpMeta = {
|
||||
data: [
|
||||
{
|
||||
mediaId: item.id
|
||||
}
|
||||
],
|
||||
seasonTitle: item.season_title,
|
||||
episodeNumber: item.episode,
|
||||
episodeTitle: item.title,
|
||||
seasonID: item.season_id,
|
||||
season: item.season_number,
|
||||
showID: id,
|
||||
e: selEpId
|
||||
};
|
||||
if(item.playback){
|
||||
epMeta.data[0].playback = item.playback;
|
||||
}
|
||||
// find episode numbers
|
||||
if((but && item.playback && !doEpsFilter.isSelected([selEpId, item.id])) || (all && item.playback) || (!but && doEpsFilter.isSelected([selEpId, item.id]) && !item.isSelected && item.playback)){
|
||||
selectedMedia.push(epMeta);
|
||||
item.isSelected = true;
|
||||
|
|
@ -1239,7 +1239,7 @@ export default class Crunchy implements ServiceClass {
|
|||
onlyVid: [],
|
||||
skipSubMux: options.skipSubMux,
|
||||
onlyAudio: [],
|
||||
output: `${options}.${options.mp4 ? 'mp4' : 'mkv'}`,
|
||||
output: `${options.output}.${options.mp4 ? 'mp4' : 'mkv'}`,
|
||||
subtitles: data.filter(a => a.type === 'Subtitle').map((a) : SubtitleInput => {
|
||||
if (a.type === 'Video')
|
||||
throw new Error('Never');
|
||||
|
|
@ -1388,6 +1388,7 @@ export default class Crunchy implements ServiceClass {
|
|||
if(item.season_title == '' && item.series_title == ''){
|
||||
item.season_title = 'NO_TITLE';
|
||||
}
|
||||
const epNum = key.startsWith('E') ? key.slice(1) : key;
|
||||
// set data
|
||||
const epMeta: CrunchyEpMeta = {
|
||||
data: [
|
||||
|
|
@ -1400,12 +1401,12 @@ export default class Crunchy implements ServiceClass {
|
|||
episodeTitle: item.title,
|
||||
seasonID: item.season_id,
|
||||
season: item.season_number,
|
||||
showID: item.series_id
|
||||
showID: item.series_id,
|
||||
e: epNum
|
||||
};
|
||||
if(item.playback){
|
||||
epMeta.data[0].playback = item.playback;
|
||||
}
|
||||
const epNum = key.startsWith('E') ? key.slice(1) : key;
|
||||
// find episode numbers
|
||||
if(item.playback && ((but && !doEpsFilter.isSelected([epNum, item.id])) || (all || (doEpsFilter.isSelected([epNum, item.id])) && !but))) {
|
||||
if (Object.prototype.hasOwnProperty.call(ret, key)) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ let mainWindow: BrowserWindow|undefined = undefined;
|
|||
export { mainWindow };
|
||||
|
||||
const createWindow = async () => {
|
||||
registerMessageHandler();
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
|
|
@ -29,6 +28,7 @@ const createWindow = async () => {
|
|||
},
|
||||
});
|
||||
|
||||
registerMessageHandler(mainWindow);
|
||||
|
||||
if (!process.env.USE_BROWSER) {
|
||||
const app = express();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { ipcMain } from 'electron';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { MessageHandler } from '../../../@types/messageHandler';
|
||||
import Crunchy from './serviceHandler/crunchyroll';
|
||||
import Funimation from './serviceHandler/funimation';
|
||||
import { mainWindow } from './';
|
||||
|
||||
export default () => {
|
||||
export default (window: BrowserWindow) => {
|
||||
let handler: MessageHandler|undefined;
|
||||
ipcMain.handle('setup', (_, data) => {
|
||||
if (data === 'funi') {
|
||||
handler = new Funimation();
|
||||
handler = new Funimation(window);
|
||||
} else if (data === 'crunchy') {
|
||||
handler = new Crunchy();
|
||||
handler = new Crunchy(window);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -22,8 +21,10 @@ export default () => {
|
|||
ipcMain.handle('availableDubCodes', async () => handler?.availableDubCodes());
|
||||
ipcMain.handle('resolveItems', async (_, data) => handler?.resolveItems(data));
|
||||
ipcMain.handle('listEpisodes', async (_, data) => handler?.listEpisodes(data));
|
||||
|
||||
ipcMain.handle('downloadItem', async (_, data) => handler?.downloadItem(data));
|
||||
/*
|
||||
setInterval(() => {
|
||||
mainWindow?.webContents.send('progress', { hello: 'World' });
|
||||
}, 1000);
|
||||
*/
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,2 +1,35 @@
|
|||
import { BrowserWindow, dialog } from "electron";
|
||||
import { ProgressData } from "../../../../@types/messageHandler";
|
||||
|
||||
export default class Base {
|
||||
|
||||
constructor(private window: BrowserWindow) {}
|
||||
|
||||
private downloading = false;
|
||||
|
||||
setDownloading(downloading: boolean) {
|
||||
this.downloading = downloading;
|
||||
}
|
||||
|
||||
getDownloading() {
|
||||
return this.downloading;
|
||||
}
|
||||
|
||||
alertError(error: Error) {
|
||||
dialog.showMessageBoxSync(this.window, {
|
||||
message: `${error.name ?? 'An error occured'}\n${error.message}`,
|
||||
detail: error.stack,
|
||||
title: `Error`,
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
handleProgress(data: ProgressData) {
|
||||
this.window.webContents.send('progress', data);
|
||||
}
|
||||
|
||||
getWindow() {
|
||||
return this.window;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
import { AuthData, CheckTokenResponse, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { CrunchyDownloadOptions } from '../../../../@types/crunchyTypes';
|
||||
import { AuthData, CheckTokenResponse, DownloadData, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import Crunchy from '../../../../crunchy';
|
||||
import Funimation from '../../../../funi';
|
||||
import { getDefault } from '../../../../modules/module.args';
|
||||
import { ArgvType } from '../../../../modules/module.app-args';
|
||||
import { buildDefault, getDefault } from '../../../../modules/module.args';
|
||||
import { dubLanguageCodes } from '../../../../modules/module.langsData';
|
||||
import Base from './base';
|
||||
|
||||
class CrunchyHandler extends Base implements MessageHandler {
|
||||
private crunchy: Crunchy;
|
||||
constructor() {
|
||||
super();
|
||||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.crunchy = new Crunchy();
|
||||
}
|
||||
|
||||
|
|
@ -30,23 +33,24 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
return res;
|
||||
return { isOk: true, value: res.value.map(a => {
|
||||
return {
|
||||
...data,
|
||||
ids: a.data.map(a => a.mediaId),
|
||||
title: a.episodeTitle,
|
||||
parent: {
|
||||
title: a.seasonTitle,
|
||||
season: a.season.toString()
|
||||
},
|
||||
...data
|
||||
e: a.e
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
||||
public async search(data: SearchData): Promise<SearchResponse> {
|
||||
this.crunchy.refreshToken();
|
||||
const funiSearch = await this.crunchy.doSearch(data);
|
||||
if (!funiSearch.isOk)
|
||||
return funiSearch;
|
||||
return { isOk: true, value: funiSearch.value };
|
||||
const crunchySearch = await this.crunchy.doSearch(data);
|
||||
if (!crunchySearch.isOk)
|
||||
return crunchySearch;
|
||||
return { isOk: true, value: crunchySearch.value };
|
||||
}
|
||||
|
||||
public async checkToken(): Promise<CheckTokenResponse> {
|
||||
|
|
@ -60,6 +64,29 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
public auth(data: AuthData) {
|
||||
return this.crunchy.doAuth(data);
|
||||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
this.setDownloading(true);
|
||||
const _default = buildDefault() as ArgvType;
|
||||
await this.crunchy.refreshToken();
|
||||
const res = await this.crunchy.downloadFromSeriesID(data.id, {
|
||||
dubLang: data.dubLang,
|
||||
e: data.e
|
||||
});
|
||||
if (res.isOk) {
|
||||
for (const select of res.value) {
|
||||
if (!(await this.crunchy.downloadEpisode(select, {..._default, skipsubs: false, callback: this.handleProgress.bind(this), q: data.q, fileName: data.fileName }))) {
|
||||
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
|
||||
er.name = 'Download error';
|
||||
this.alertError(er);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.alertError(res.reason);
|
||||
}
|
||||
this.getWindow().webContents.send('finish');
|
||||
this.setDownloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
export default CrunchyHandler;
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
import { AuthData, CheckTokenResponse, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { AuthData, CheckTokenResponse, DownloadData, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import Funimation from '../../../../funi';
|
||||
import { getDefault } from '../../../../modules/module.args';
|
||||
import { ArgvType } from '../../../../modules/module.app-args';
|
||||
import { buildDefault, getDefault } from '../../../../modules/module.args';
|
||||
import { dubLanguageCodes } from '../../../../modules/module.langsData';
|
||||
import Base from './base';
|
||||
|
||||
class FunimationHandler extends Base implements MessageHandler {
|
||||
private funi: Funimation;
|
||||
constructor() {
|
||||
super();
|
||||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.funi = new Funimation();
|
||||
}
|
||||
|
||||
|
|
@ -43,13 +45,14 @@ class FunimationHandler extends Base implements MessageHandler {
|
|||
return res;
|
||||
return { isOk: true, value: res.value.map(a => {
|
||||
return {
|
||||
...data,
|
||||
ids: [a.episodeID],
|
||||
title: a.title,
|
||||
parent: {
|
||||
title: a.seasonTitle,
|
||||
season: a.seasonNumber
|
||||
},
|
||||
...data
|
||||
e: a.episodeID
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
|
@ -75,6 +78,20 @@ class FunimationHandler extends Base implements MessageHandler {
|
|||
public auth(data: AuthData) {
|
||||
return this.funi.auth(data);
|
||||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
this.setDownloading(true);
|
||||
const res = await this.funi.getShow(false, { all: false, but: false, id: parseInt(data.id), e: data.e });
|
||||
const _default = buildDefault() as ArgvType;
|
||||
if (!res.isOk)
|
||||
return this.alertError(res.reason);
|
||||
|
||||
for (const ep of res.value) {
|
||||
await this.funi.getEpisode(false, { dubLang: data.dubLang, fnSlug: ep, s: data.id, subs: { dlsubs: ['all'], sub: false }, callback: this.handleProgress.bind(this) }, { ..._default, ass: true, callback: this.handleProgress.bind(this), fileName: data.fileName, q: data.q })
|
||||
}
|
||||
this.getWindow().webContents.send('finish');
|
||||
this.setDownloading(false);
|
||||
};
|
||||
}
|
||||
|
||||
export default FunimationHandler;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import React from "react";
|
||||
import { ProgressData } from "../../../../../../@types/messageHandler";
|
||||
import useStore from "../../../hooks/useStore";
|
||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
||||
|
||||
const useDownloadManager = () => {
|
||||
const [ { currentDownload }, dispatch ] = useStore();
|
||||
const messageHandler = React.useContext(messageChannelContext);
|
||||
|
||||
const [progressData, setProgressData] = React.useState<ProgressData|undefined>();
|
||||
|
||||
React.useEffect(() => {
|
||||
const handler = (data: ProgressData) => {
|
||||
console.log(data);
|
||||
setProgressData(data);
|
||||
}
|
||||
messageHandler?.randomEvents.on('progress', handler);
|
||||
|
||||
const finishHandler = () => {
|
||||
console.log('DONE');
|
||||
}
|
||||
|
||||
messageHandler?.randomEvents.on('finish', finishHandler);
|
||||
return () => {
|
||||
messageHandler?.randomEvents.removeListener('progress', handler);
|
||||
messageHandler?.randomEvents.removeListener('finish', finishHandler)
|
||||
};
|
||||
}, [messageHandler]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!currentDownload)
|
||||
return;
|
||||
messageHandler?.downloadItem(currentDownload);
|
||||
}, [currentDownload]);
|
||||
|
||||
|
||||
return progressData;
|
||||
}
|
||||
|
||||
export default useDownloadManager;
|
||||
|
|
@ -3,7 +3,6 @@ import { Box, Button, Checkbox, Chip, FormControl, FormControlLabel, IconButton,
|
|||
import useStore from "../../../hooks/useStore";
|
||||
import MultiSelect from "../../MultiSelect";
|
||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
||||
import { Check, Close } from "@mui/icons-material";
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import React from "react";
|
||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
||||
|
||||
import { ProgressData } from '../../../../../../@types/hls-download';
|
||||
import useDownloadManager from "../DownloadManager/DownloadManager";
|
||||
|
||||
const Progress: React.FC = () => {
|
||||
const messageHandler = React.useContext(messageChannelContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handler = (data: ProgressData) => {
|
||||
|
||||
}
|
||||
messageHandler?.randomEvents.on('progress', handler);
|
||||
return () => messageHandler?.randomEvents.removeListener('progress', handler);
|
||||
}, [messageHandler]);
|
||||
useDownloadManager();
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const MenuProps = {
|
|||
function getStyles(name: string, personName: readonly string[], theme: Theme) {
|
||||
return {
|
||||
fontWeight:
|
||||
personName.indexOf(name) === -1
|
||||
(personName ?? []).indexOf(name) === -1
|
||||
? theme.typography.fontWeightRegular
|
||||
: theme.typography.fontWeightMedium
|
||||
};
|
||||
|
|
@ -39,7 +39,7 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
|
|||
labelId="multi-select-label"
|
||||
id="multi-select"
|
||||
multiple
|
||||
value={props.selected}
|
||||
value={(props.selected ?? [])}
|
||||
onChange={e => {
|
||||
const val = typeof e.target.value === "string" ? e.target.value.split(",") : e.target.value;
|
||||
if (props.allOption && val.includes('all')) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ export class RandomEventHandler {
|
|||
private handler: {
|
||||
[eventName in keyof RandomEvents]: Handler<RandomEvents[eventName]>[]
|
||||
} = {
|
||||
progress: []
|
||||
progress: [],
|
||||
finish: []
|
||||
};
|
||||
private allHandler: Handler<unknown>[] = [];
|
||||
|
||||
|
|
@ -52,6 +53,10 @@ const MessageChannelProvider: React.FC = ({ children }) => {
|
|||
const { ipcRenderer } = (window as any).Electron as { ipcRenderer: IpcRenderer };
|
||||
const [ randomEventHandler ] = React.useState(new RandomEventHandler());
|
||||
|
||||
const buildListener = (event: keyof RandomEvents) => {
|
||||
return (_: IpcRendererEvent, ...data: any[]) => randomEventHandler.emit(event, data.length === 0 ? undefined : data[0]);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
const currentService = await ipcRenderer.invoke('type');
|
||||
|
|
@ -63,9 +68,14 @@ const MessageChannelProvider: React.FC = ({ children }) => {
|
|||
}, [store.service])
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = (_: IpcRendererEvent, ...data: any[]) => randomEventHandler.emit('progress', data.length === 0 ? undefined : data[0]);
|
||||
ipcRenderer.on('progress', listener);
|
||||
return () => ipcRenderer.removeListener('progress', listener) as unknown as void;
|
||||
const progressListener = buildListener('progress');
|
||||
const finishListener = buildListener('finish');
|
||||
ipcRenderer.on('progress', progressListener);
|
||||
ipcRenderer.on('finish', finishListener);
|
||||
return () => {
|
||||
ipcRenderer.removeListener('progress', progressListener);
|
||||
ipcRenderer.removeListener('finish', finishListener);
|
||||
};
|
||||
}, [ ipcRenderer ]);
|
||||
|
||||
|
||||
|
|
@ -77,7 +87,8 @@ const MessageChannelProvider: React.FC = ({ children }) => {
|
|||
availableDubCodes: async () => await ipcRenderer.invoke('availableDubCodes'),
|
||||
resolveItems: async (data) => await ipcRenderer.invoke('resolveItems', data),
|
||||
listEpisodes: async (data) => await ipcRenderer.invoke('listEpisodes', data),
|
||||
randomEvents: randomEventHandler
|
||||
randomEvents: randomEventHandler,
|
||||
downloadItem: (data) => ipcRenderer.invoke('downloadItem', data)
|
||||
}
|
||||
|
||||
return <messageChannelContext.Provider value={messageHandler}>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ export type StoreState = {
|
|||
queue: QueueItem[],
|
||||
episodeListing: Episode[];
|
||||
downloadOptions: DownloadOptions,
|
||||
service: 'crunchy'|'funi'|undefined
|
||||
service: 'crunchy'|'funi'|undefined,
|
||||
currentDownload?: QueueItem
|
||||
}
|
||||
|
||||
export type StoreAction<T extends keyof StoreState> = {
|
||||
|
|
@ -27,7 +28,12 @@ export type StoreAction<T extends keyof StoreState> = {
|
|||
const Reducer = <T extends keyof StoreState,>(state: StoreState, action: StoreAction<T>): StoreState => {
|
||||
switch(action.type) {
|
||||
case "queue":
|
||||
return { ...state, queue: state.queue.concat(action.payload as QueueItem[]) };
|
||||
let queue = state.queue.concat(action.payload as QueueItem[]);
|
||||
if (!state.currentDownload && queue.length > 0) {
|
||||
state.currentDownload = queue[0];
|
||||
queue = queue.slice(1);
|
||||
}
|
||||
return { ...state, queue };
|
||||
default:
|
||||
return { ...state, [action.type]: action.payload }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -588,9 +588,27 @@ const getDefault = <T extends boolean|string|number|unknown[]>(name: string, cfg
|
|||
}
|
||||
};
|
||||
|
||||
const buildDefault = () => {
|
||||
const data: Record<string, unknown> = {}
|
||||
const defaultArgs = args.filter(a => a.default);
|
||||
defaultArgs.forEach(item => {
|
||||
if (typeof item.default === 'object') {
|
||||
if (Array.isArray(item.default)) {
|
||||
data[item.name] = item.default;
|
||||
} else {
|
||||
data[item.default.name ?? item.name] = item.default.default;
|
||||
}
|
||||
} else {
|
||||
data[item.name] = item.default;
|
||||
}
|
||||
})
|
||||
return data;
|
||||
}
|
||||
|
||||
export {
|
||||
TAppArg,
|
||||
getDefault,
|
||||
buildDefault,
|
||||
args,
|
||||
groups,
|
||||
availableFilenameVars
|
||||
|
|
|
|||
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -17,7 +17,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"got": "^11.8.3",
|
||||
"hls-download": "^2.6.7",
|
||||
"hls-download": "^2.6.8",
|
||||
"iso-639": "^0.2.2",
|
||||
"lookpath": "^1.1.0",
|
||||
"m3u8-parsed": "^1.3.0",
|
||||
|
|
@ -6640,9 +6640,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hls-download": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/hls-download/-/hls-download-2.6.7.tgz",
|
||||
"integrity": "sha512-74etN9G6LjDgCCuv0cvNPIUXOFNHWxXA6tyu10/cZSn6wYhF7L8c4QnE/MKpWr557HIDaQJJjWsg8NBAre24LQ==",
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/hls-download/-/hls-download-2.6.8.tgz",
|
||||
"integrity": "sha512-2ji6wzZpEGOL5C+VRhKmSbUvXER1MGnEgpknoetPgpvmAiV6OwbsIVhYo/t5tHTnaeZ0H2dnE5w6nF6Nn0Ij7w==",
|
||||
"dependencies": {
|
||||
"got": "^11.8.3",
|
||||
"proxy-agent": "^5.0.0",
|
||||
|
|
@ -18364,9 +18364,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"hls-download": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/hls-download/-/hls-download-2.6.7.tgz",
|
||||
"integrity": "sha512-74etN9G6LjDgCCuv0cvNPIUXOFNHWxXA6tyu10/cZSn6wYhF7L8c4QnE/MKpWr557HIDaQJJjWsg8NBAre24LQ==",
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/hls-download/-/hls-download-2.6.8.tgz",
|
||||
"integrity": "sha512-2ji6wzZpEGOL5C+VRhKmSbUvXER1MGnEgpknoetPgpvmAiV6OwbsIVhYo/t5tHTnaeZ0H2dnE5w6nF6Nn0Ij7w==",
|
||||
"requires": {
|
||||
"got": "^11.8.3",
|
||||
"proxy-agent": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"got": "^11.8.3",
|
||||
"hls-download": "^2.6.7",
|
||||
"hls-download": "^2.6.8",
|
||||
"iso-639": "^0.2.2",
|
||||
"lookpath": "^1.1.0",
|
||||
"m3u8-parsed": "^1.3.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue